<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="刘清政">
  <meta name="keyword" content="hexo-theme">
  
    <link rel="shortcut icon" href="/css/images/logo.png">
  
  <title>
    
      Go内存模型 | Justin-刘清政的博客
    
  </title>
  <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/tomorrow.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
    
<link rel="stylesheet" href="/css/plugins/gitment.css">

  
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
    
<script src="/js/gitment.js"></script>

  
  

  
<meta name="generator" content="Hexo 4.2.0"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>

  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>Justin-刘清政的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于我</a>
          
        </li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于我</a>
            
          </li>
        
      </ul>
    </div>
  </div>
</header>

    <div id="article-banner">
  <h2>Go内存模型</h2>



  <p class="post-date">2019-03-10</p>
    <!-- 不蒜子统计 -->
    <span id="busuanzi_container_page_pv" style='display:none' class="">
        <i class="icon-smile icon"></i> 阅读数：<span id="busuanzi_value_page_pv"></span>次
    </span>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><h2 id="内存模型介绍"><a href="#内存模型介绍" class="headerlink" title="内存模型介绍"></a>内存模型介绍</h2><p>Go语言的内存模型规定了一种规则。这种规则可以保证，在一个goroutine读取某个变量的值，是其他goroutine对同一个变量写入的值。</p>
<h2 id="建议"><a href="#建议" class="headerlink" title="建议"></a>建议</h2><p>程序中多个goroutine同时修改相同数据，必须使之能够按序访问。为了按序访问以及保护数据，可以使用channel操作或者其他同步原语例如sync及sync/atomic包。</p>
<p>If you must read the rest of this document to understand the behavior of your program, you are being too clever.</p>
<p>Don’t be clever.</p>
<h2 id="Happens-Before原则"><a href="#Happens-Before原则" class="headerlink" title="Happens-Before原则"></a>Happens-Before原则</h2><p>在单个goroutine中，读取和写入的操作一定与程序的执行顺序一致。 也就是说，仅当重新排序语句不会改变语言规范所定义的该goroutine中的行为时，编译器和处理器才可以对单个goroutine中执行的读取和写入进行重新排序。 由于此重新排序，一个goroutine观察到的执行顺序可能不同于另一个goroutine所察觉的执行顺序。 例如，如果一个goroutine执行<code>a = 1； b = 2；</code>，另一个goroutine可能会a的值更新前，就已经观察到b的更新后的值。</p>
<p>在Go程序中，为了限定读取和写入的要求，我们定义<code>happens-before</code>原则，此原则限定了程序在读写内存时的一种偏序关系。 如果事件e1发生在事件e2之前，那么我们说e2发生在e1之后。 同样，如果e1不在e2之前发生并且在e2之后也没有发生，那么我们说e1和e2同时发生。</p>
<blockquote>
<p>Happen-Before是一个术语，不仅仅是go才有的。</p>
<p>原始的定义是由Leslie Lamport在1978年的文章 <a href="https://links.jianshu.com/go?to=http%3A%2F%2Fresearch.microsoft.com%2Fen-us%2Fum%2Fpeople%2Flamport%2Fpubs%2Ftime-clocks.pdf" target="_blank" rel="noopener">Time, Clocks and the Ordering of Events in a Distributed System</a>中提出来的</p>
<p>另见维基百科 <a href="https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FHappened-before" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Happened-before</a></p>
</blockquote>
<p><strong>在单个goroutine中，happens-before发生顺序即是程序中的顺序。</strong></p>
<p>如果同时满足下述两个规则，那么对变量v的读操作r能够观察到对变量v的写操作w：</p>
<ol>
<li>读操作r没有发生在写操作w之前</li>
<li>没有另外一个针对变量v的写操作w`发生在，写操作w之后且在读操作r之前</li>
</ol>
<p>为了保证对变量v的读操作r能够观察到对变量v的特定的写操作w的值，即确保读操作r可以观察到写操作w是唯一的对变量v的写操作。 那么，如果满足下述两个原则，读操作r将能够观察到写操作w</p>
<ol>
<li>w发生在r之前</li>
<li>任何其他的对共享变量v的写操作w`发生在写操作w之前或者读操作r之后</li>
</ol>
<p>这对条件的约束强于前面一对条件的约束；它需要没有其他任何的写操作为w`，与写操作w 或 读操作r同时发生。</p>
<p>在单一的goroutine中，由于没有并发，因此两个定义也是等价的：读操作r可以观察到最近的写操作w写入到v的值。当多个goroutine访问一个共享变量v时，必须使用同步事件建立<code>happens-before</code>条件来确保读操作观察到期望的写操作的变化。</p>
<p>使用变量v的类型零值初始化变量v的行为与内存模型中写操作相同。</p>
<p>如果读取和写入的值大于一个机器字,那么多个机器字节的操作顺序不是固定的顺序。</p>
<h2 id="同步"><a href="#同步" class="headerlink" title="同步"></a>同步</h2><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>程序初始化操作是在单个goroutine中，但是该goroutine可能会创建的其他goroutine，这些goroutines是并发运行的。</p>
<p><strong>如果包p导入了包q，则q包中的的init函数的完成发生在任何p包中的init函数开始之前。</strong></p>
<p><strong>函数main.main的启动发生在所有init函数完成之后。</strong></p>
<h3 id="Goroutine创建"><a href="#Goroutine创建" class="headerlink" title="Goroutine创建"></a>Goroutine创建</h3><p><strong>使用go语句启动新的goroutine发生在此goroutine执行前。</strong></p>
<p>例如下面的程序</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">var a string</span><br><span class="line"></span><br><span class="line">func f() &#123;</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func hello() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    go f()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>调用<code>hello()</code>函数会在某个时刻打印<code>hello, world</code>字符串（也可能打印字符串的时刻发生在hello函数返回之后）</p>
<h3 id="Goroutine销毁"><a href="#Goroutine销毁" class="headerlink" title="Goroutine销毁"></a>Goroutine销毁</h3><p>不能保证goroutine的退出在程序中发生任何事件之前发生。 例如下面的程序中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">var a string</span><br><span class="line"></span><br><span class="line">func hello() &#123;</span><br><span class="line">    go func() &#123; a &#x3D; &quot;hello&quot; &#125;()</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>对变量a的赋值没有跟随任何同步事件之后, 所以不能保证会被其他goroutine观察到。 事实上,一个激进的编译器可能会删除整个go声明语句。</p>
<p>如果想让一个goroutine的执行操作影响必须对另外一个goroutine可见， 可以使用同步机制（例如lock或者channel通信）来确定相对的顺序。</p>
<h3 id="Channel-通信"><a href="#Channel-通信" class="headerlink" title="Channel 通信"></a>Channel 通信</h3><p>channel通信是goroutine之间同步数据的主要方法。 通常在不同的goroutine中，将特定channel上的每个发送与该channel上的相应接收进行匹配。</p>
<p><strong>channel上的发送 happens-before 该channel上的相应接收操作完成。</strong></p>
<p>下面程序</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">var c &#x3D; make(chan int, 10)</span><br><span class="line">var a string</span><br><span class="line"></span><br><span class="line">func f() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    c &lt;- 0</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    go f()</span><br><span class="line">    &lt;-c</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这段程序一定能够打印出“hello，world”。 对a的写先于对c信息的发送，c的发送一定会先于c接受的完成，因此这些都先于print调用完成</p>
<blockquote>
<p><em>happens before</em> 具有传递性</p>
</blockquote>
<p><strong>关闭一个channel happens-before channel收到返回零值， 因为channel已经关闭了</strong></p>
<p>在前面的例子中，如果使用<code>close(c)</code>代替<code>c &lt;- 0</code>，能够保证相同的行为。</p>
<p><strong>从一个无缓冲的channel接收数据 happens-before 向这个channel完成发送数据之前</strong></p>
<p>下面这段程序（如前面的代码，但是使用无缓冲的channel）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">var c &#x3D; make(chan int)</span><br><span class="line">var a string</span><br><span class="line"></span><br><span class="line">func f() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    &lt;-c</span><br><span class="line">&#125;</span><br><span class="line">func main() &#123;</span><br><span class="line">    go f()</span><br><span class="line">    c &lt;- 0</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这段程序一定能够打印出“hello，world”。对变量a的写操作发生在channel变量c的接收之前， c的接收发生在c的发送之前，c的发送发生在print打印之前。</p>
<p>如果是带缓冲的channel（例如<code>c = make(chan int, 1)</code>)，那么程序则不一定能保证会打印“hello，world”。（可能打印空字符串或者crash、或者其他未知情况）</p>
<p><strong>在缓冲大小为C的chanel上接收第k个数据 happens-before 在第 k + C发送完成之前。</strong></p>
<p>该规则将前一个规则(无缓冲的channel)推广到缓冲channel。 它允许通过缓冲的channel对计数信号量进行建模：channel中的项目数量对应于活动使用的数量，channel的容量对应于同时使用的最大数量，发送一个项目获取信号量，以及 接收项目会释放信号量。 这是限制并发性的常见用法。</p>
<p>该程序为工作列表中的每个条目启动一个goroutine，但是goroutine使用限制channel进行协调，以确保一次最多运行三个work。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">var limit &#x3D; make(chan int, 3)</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    for _, w :&#x3D; range work &#123;</span><br><span class="line">        go func(w func()) &#123;</span><br><span class="line">            limit &lt;- 1</span><br><span class="line">            w()</span><br><span class="line">            &lt;-limit</span><br><span class="line">        &#125;(w)</span><br><span class="line">    &#125;</span><br><span class="line">    select&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Locks-锁"><a href="#Locks-锁" class="headerlink" title="Locks 锁"></a>Locks 锁</h3><p>sync包实现了两种类型的锁：互斥锁（sync.Mutex）及读写锁（sync.RWMutex）</p>
<p><strong>对于任意 sync.Mutex 或 sync.RWMutex 类型的变量l。 如果 n &lt; m ，那么第n次 l.Unlock() 调用在第 m次 l.Lock()调用返回前发生。</strong></p>
<p>下面程序</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var l sync.Mutex</span><br><span class="line">var a string</span><br><span class="line"></span><br><span class="line">func f() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    l.Unlock()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    l.Lock()</span><br><span class="line">    go f()</span><br><span class="line">    l.Lock()</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>程序一定可以输出”hello, world”。第一次调用<code>l.Unlock()</code>(在函数f中)发生在第二次调用<code>l.Lock()</code>之前（在main函数中），第二次调用<code>l.Lock()</code>发生在print之前。</p>
<blockquote>
<p>上面的例子相当于 n=1, m=2</p>
</blockquote>
<p><strong>在一个sync.RWMutex变量l上任何调用 l.RLock，都有一个n，使得l.RLock在调用n个l.Unlock之后发生（并返回），并且匹配的l.RUnlock发生在调用第n + 1个l.Lock之前。</strong></p>
<h3 id="Once"><a href="#Once" class="headerlink" title="Once"></a>Once</h3><p>这个同步包为多个goroutines进行初始化只执行一次的操作，提供了一种安全的机制，多个线程可以执行<code>once.Do(f)</code>，那么函数会且只会有一个执行f函数，而其他调用将阻塞直到f函数的返回。</p>
<p><strong>使用once.Do(f)执行f()并返回，发生在任何调用once.Do(f)返回之前。</strong></p>
<p>下面程序</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">var a string</span><br><span class="line">var once sync.Once</span><br><span class="line"></span><br><span class="line">func setup() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func doprint() &#123;</span><br><span class="line">    once.Do(setup)</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func twoprint() &#123;</span><br><span class="line">    go doprint()</span><br><span class="line">    go doprint()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>调用twoprint将只调用一次setup函数。 setup函数将在两次调用打印之前完成。 结果将是“ hello，world”将被打印两次。</p>
<h2 id="错误的同步"><a href="#错误的同步" class="headerlink" title="错误的同步"></a>错误的同步</h2><p>注意，读操作r可能会观察到与r同时发生的写操作w写入的值。 即使发生这种情况，也并不意味着在r之后发生的读取将观察到在w之前发生的写入。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">var a, b int</span><br><span class="line"></span><br><span class="line">func f() &#123;</span><br><span class="line">    a &#x3D; 1</span><br><span class="line">    b &#x3D; 2</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func g() &#123;</span><br><span class="line">    print(b)</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    go f()</span><br><span class="line">    g()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有可能打印出2和0</p>
<blockquote>
<p>即b已经赋值为2，但是a还未被赋值</p>
</blockquote>
<p>这个事实使一些常见的习语无效。</p>
<p>双检锁是为了避免同步的开销，例如,twoprint程序可能会错误地写成</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">var a string</span><br><span class="line">var done bool</span><br><span class="line"></span><br><span class="line">func setup() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    done &#x3D; true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func doprint() &#123;</span><br><span class="line">    if !done &#123;</span><br><span class="line">        once.Do(setup)</span><br><span class="line">    &#125;</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func twoprint() &#123;</span><br><span class="line">    go doprint()</span><br><span class="line">    go doprint()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里不能保证在doprint函数里面，能够观察到变量done的值，就意味着能够观察到对a的赋值变化。 这里可能会输出空字符串，而不是希望的<code>hello, world</code>。</p>
<p>下述为另外一个错误示例（一直忙等）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var a string</span><br><span class="line">var done bool</span><br><span class="line"></span><br><span class="line">func setup() &#123;</span><br><span class="line">    a &#x3D; &quot;hello, world&quot;</span><br><span class="line">    done &#x3D; true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    go setup()</span><br><span class="line">    for !done &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    print(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述示例所示，在主函数main中，观察到done值为true并不能保证一定能看到对a的赋值变化, 因此本示例程序也有可能打印空值。 更糟糕的是，setup函数对done=true的赋值，并不一定能被main函数观察到，因此这两个线程之间并没有使用任何的同步事件。 因此for循环在主函数中并不能保证一定会循环结束。</p>
<p>下述为一些更为难以发现的变体</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">type T struct &#123;</span><br><span class="line">    msg string</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var g *T</span><br><span class="line"></span><br><span class="line">func setup() &#123;</span><br><span class="line">    t :&#x3D; new(T)</span><br><span class="line">    t.msg &#x3D; &quot;hello, world&quot;</span><br><span class="line">    g &#x3D; t</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    go setup()</span><br><span class="line">    for g &#x3D;&#x3D; nil &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    print(g.msg)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>即使主函数观察到<code>g != nil</code>且退出了循环，也不能保证main能够观察到<code>g.msg</code>的赋值内容。</p>
<p>对于上述的所有错误示例，都可以采用同样的解决办法：使用明确的同步方式。</p>
</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#Go" >
    <span class="tag-code">Go</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/go/7-%E5%85%B6%E4%BB%96/03-Go%E5%8C%85%E7%AE%A1%E7%90%86%E8%AF%A6%E8%A7%A3/">
        <span class="nav-arrow">← </span>
        
          Go包管理
        
      </a>
    
    
      <a class="nav-right" href="/go/2-Go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/36-%E5%86%99%E5%85%A5%E6%96%87%E4%BB%B6/">
        
          36-写入文件
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">点击上方按钮,请我喝杯咖啡！</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Gitment START -->
      <div id="comments"></div>
      <!-- Gitment END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
  <!-- 不蒜子统计 -->
    <strong class="toc-title">目录</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#内存模型介绍"><span class="toc-nav-text">内存模型介绍</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#建议"><span class="toc-nav-text">建议</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#Happens-Before原则"><span class="toc-nav-text">Happens-Before原则</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#同步"><span class="toc-nav-text">同步</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#初始化"><span class="toc-nav-text">初始化</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#Goroutine创建"><span class="toc-nav-text">Goroutine创建</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#Goroutine销毁"><span class="toc-nav-text">Goroutine销毁</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#Channel-通信"><span class="toc-nav-text">Channel 通信</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#Locks-锁"><span class="toc-nav-text">Locks 锁</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#Once"><span class="toc-nav-text">Once</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#错误的同步"><span class="toc-nav-text">错误的同步</span></a></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://www.liuqingzheng.top/go/5-Go语言进阶/01-Go内存模型/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', 'http://file.muyutech.com/error-img.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== 'http://file.muyutech.com/error-img.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>



  <script>
    var gitmentConfig = "liuqingzheng";
    if (gitmentConfig !== 'undefined') {
      var gitment = new Gitment({
        id: "Go内存模型",
        owner: "liuqingzheng",
        repo: "FuckBlog",
        oauth: {
          client_id: "32a4076431cf39d0ecea",
          client_secret: "94484bd79b3346a949acb2fda3c8a76ce16990c6"
        },
        theme: {
          render(state, instance) {
            const container = document.createElement('div')
            container.lang = "en-US"
            container.className = 'gitment-container gitment-root-container'
            container.appendChild(instance.renderHeader(state, instance))
            container.appendChild(instance.renderEditor(state, instance))
            container.appendChild(instance.renderComments(state, instance))
            container.appendChild(instance.renderFooter(state, instance))
            return container;
          }
        }
      })
      gitment.render(document.getElementById('comments'))
    }
  </script>




    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
<!-- 不蒜子统计 -->
<span id="busuanzi_container_site_pv">
     本站总访问量<span id="busuanzi_value_site_pv"></span>次
</span>
<span class="post-meta-divider">|</span>
<span id="busuanzi_container_site_uv" style='display:none'>
     本站访客数<span id="busuanzi_value_site_uv"></span>人
</span>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>



  <p class="copyright">
    &copy; 2021 | Proudly powered by <a href="https://www.cnblogs.com/xiaoyuanqujing" target="_blank">小猿取经</a>
    <br>
    Theme by <a href="https://www.cnblogs.com/xiaoyuanqujing" target="_blank" rel="noopener">小猿取经</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>





<!-- Baidu Tongji -->

<script>
    var _baId = 'c5fd96eee1193585be191f318c3fa725';
    // Originial
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "//hm.baidu.com/hm.js?" + _baId;
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
</script>


<script src="/js/script.js"></script>


<script src="/js/search.js"></script>


<script src="/js/load.js"></script>



  <span class="local-search local-search-google local-search-plugin" style="right: 50px;top: 70px;;position:absolute;z-index:2;">
      <input type="search" placeholder="站内搜索" id="local-search-input" class="local-search-input-cls" style="">
      <div id="local-search-result" class="local-search-result-cls"></div>
  </span>


  </body>
</html>